# -*- coding: utf-8 -*

import datetime
import platform
import random
import StringIO
from PIL import Image, ImageDraw, ImageFont
from django.utils.timezone import utc
from dateutil import tz
from dateutil.relativedelta import relativedelta
from umeng_tool import settings
from pytz import timezone
from delorean import Delorean, parse

class RetPacker:
	"""
	返回数据管理
	"""
	STATUS_SUCCESS = 0
	STATUS_FAIL = -1

	@classmethod
	def pack_ret(cls, retcode, msg='', data={}):
		"""
		打包返回数据为字典
		:param retcode: 状态码
		:param msg: 消息
		:param data: 附加数据
		:return: 返回数据字典
		:rtype: dict
		"""
		return {
			'retcode': retcode,
			'msg': msg,
			'data': data
		}

	@staticmethod
	def restful_auto(method):
		"""
		装饰器：将返回的数据对象转为json字串，并返回响应对象
		:rtype: HttpResponse
		"""
		import json
		from django.http import HttpResponse

		def wrapper(*args, **kwargs):
			ret = method(*args, **kwargs)
			return HttpResponse(json.dumps(ret))
		return wrapper


class DatetimeUtils(object):

	local_tz_str = settings.TIME_ZONE
	local_tz = timezone(local_tz_str)

	@classmethod
	def get_local_datetime_from_utc_str(cls, datetime_str):
		"""
		把UTC时间的字符串转化为本地datetime对象
		"""
		dt = parse(datetime_str)
		return dt.shift(cls.local_tz_str).datetime
	
	@classmethod
	def get_utc_datetime_from_utc_str(cls, datetime_str, dt_format='%Y-%m-%d %H:%M'):
		"""
		把UTC时间的字符串转化为utc datetime对象
		"""
		dt_utc = parse(datetime_str).shift('utc').datetime
		return dt_utc
	
	@classmethod
	def get_utc_datetime_from_local_str(cls, datetime_str, dt_format='%Y-%m-%d %H:%M'):
		utc_zone = tz.tzutc()
		local_zone = tz.tzlocal()
		# Convert time string to datetime
		local_time = datetime.datetime.strptime(datetime_str, dt_format)
		# Tell the datetime object that it's in local time zone since 
		local_time = local_time.replace(tzinfo=local_zone)
		# Convert time to UTC
		utc_time = local_time.astimezone(utc_zone)
		return utc_time

	@classmethod
	def to_local_datetime(cls, utc_datetime):
		return Delorean(datetime=utc_datetime).shift(cls.local_tz_str).datetime

	@classmethod
	def to_utc_datetime(cls, local_datetime):
		utc_zone = tz.tzutc()
		return local_datetime.astimezone(utc_zone)

	@classmethod
	def now_utc(cls):
		return Delorean().datetime

	@classmethod
	def now_local(cls):
		return Delorean().shift(cls.local_tz_str).datetime

	@classmethod
	def start_of_date_local(cls, date_str=None, year=None, month=None, day=None):
		if date_str:
			d = parse(date_str)
		elif year and month and day:
			date_str = "%d-%d-%d" % (year, month, day)
			d = parse(date_str)
		else:
			return None
		return d.shift(cls.local_tz_str).truncate('day').datetime

	@classmethod
	def end_of_date_local(cls, date_str=None, year=None, month=None, day=None):
		start = cls.start_of_date_local(date_str, year, month, day)
		return start + datetime.timedelta(hours=23, minutes=59, seconds=59, microseconds=999999) if start else None

	@classmethod
	def start_of_date_utc(cls, date_str=None, year=None, month=None, day=None):
		local_start = cls.start_of_date_local(date_str, year, month, day)
		if not local_start:
			return None
		return Delorean(datetime=local_start).shift('utc').datetime

	@classmethod
	def end_of_date_utc(cls, date_str=None, year=None, month=None, day=None): 
		return Delorean(datetime=cls.end_of_date_local(date_str, year, month, day)).shift('utc').datetime
	
	@classmethod
	def start_of_today_local(cls):
		today_str = datetime.date.today().strftime("%Y-%m-%d")
		return cls.start_of_date_local(today_str)

	@classmethod
	def end_of_today_local(cls):
		today_str = datetime.date.today().strftime("%Y-%m-%d")
		return cls.end_of_date_local(today_str)

	@classmethod
	def start_of_today_utc(cls):
		today_str = datetime.date.today().strftime("%Y-%m-%d")
		return cls.start_of_date_utc(today_str)

	@classmethod
	def end_of_today_utc(cls):
		today_str = datetime.date.today().strftime("%Y-%m-%d")
		return cls.end_of_date_utc(today_str)

	@classmethod
	def prepare_time_range_local(cls, start_date_str, end_date_str, days_gap=30, date_format='%Y-%m-%d'):
		if not end_date_str:
			end_date = datetime.date.today()
			end_date_str = end_date.strftime(date_format)
		else:
			end_date = datetime.datetime.strptime(end_date_str, date_format).date()
#		end_time = datetime.datetime.combine(end_date, datetime.time.max).replace(tzinfo=cls.local_tz)
		end_time = cls.end_of_date_local(end_date_str)

		if not start_date_str:
			start_date = end_date - datetime.timedelta(days=days_gap)
			start_date_str = start_date.strftime(date_format)
		else:
			start_date = datetime.datetime.strptime(start_date_str, date_format).date()
#		start_time = datetime.datetime.combine(start_date, datetime.time.min).replace(tzinfo=cls.local_tz)
		start_time = cls.start_of_date_local(start_date_str)	
		return start_date_str, start_date, start_time, end_date_str, end_date, end_time

	@classmethod
	def prepare_time_range_utc(cls, start_date_str, end_date_str, days_gap=30, date_format='%Y-%m-%d'):
		start_date_str, start_date, start_time_local, end_date_str, end_date, end_time_local = cls.prepare_time_range_local(start_date_str, end_date_str, days_gap, date_format)

		end_time_utc = Delorean(datetime=end_time_local).shift('utc').datetime
		start_time_utc = Delorean(datetime=start_time_local).shift('utc').datetime

		return start_date_str, start_date, start_time_utc, end_date_str, end_date, end_time_utc
	
	
	@classmethod
	def local_datetime_from_timestamp(cls, timestamp):
		return datetime.datetime.fromtimestamp(timestamp).replace(tzinfo=cls.local_tz)

	@classmethod
	def utc_datetime_from_timestamp(cls, timestamp):
		return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=utc)
		
		
	@classmethod
	def humanized_time_str_for_utc_datetime(cls, utc_datetime):
		now = cls.now_utc()
		delta = now - utc_datetime
		days = delta.days
		mins = delta.seconds // 60 
		hours = mins // 60
		if days >= 1:
			return '%d天前' % days
		elif hours >= 1:
			return '%d小时前' % hours
		else:
			return "%d分钟前" % mins
		
	@classmethod
	def humanized_localized_weixin_time_str_for_utc_datetime(cls, utc_datetime, only_day_info=False):
		that_local_time = DatetimeUtils.to_local_datetime(utc_datetime)
		now_local = cls.now_local()
		that_local_date = that_local_time.date()
		now_local_date = now_local.date()

		delta = now_local_date - that_local_date
		days = delta.days
		that_weekday = that_local_date.weekday()
		now_weekday = now_local_date.weekday()
		time_str = that_local_time.strftime('%H:%M')
		weekday_chn_name = {0:'一',1:'二',2:'三',3:'四',4:'五',5:'六',6:'天'}
		if days == 0:
			result = '今天'
		elif days == 1:
			result = '昨天'
		elif days == 2:
			result = '前天'
		elif days <= now_weekday:
			result = "星期"+ weekday_chn_name.get(that_weekday, '?')
		elif days <= now_weekday + 7:
			result = "上周"+ weekday_chn_name.get(that_weekday, '?')
		else:
			result = that_local_time.strftime('%m-%d')
		if not only_day_info:
			result += ' ' + time_str
		return result
		
		
	@classmethod
	def start_and_end_date_of_one_week(cls, one_date_point):
		"""获取一周的开始日期（那周周一）和结束日期（那周周日）"""
		monday_of_that_week = one_date_point + datetime.timedelta(days=-one_date_point.weekday())	#monday -> 0
		sunday_of_that_week = monday_of_that_week + datetime.timedelta(days=6)	# sunday -> 6
		return monday_of_that_week, sunday_of_that_week
	
	
	@classmethod
	def start_and_end_datetime_of_one_week(cls, one_date_point):
		"""获取一周的开始时间（那周周一0点正）和结束时间（那周周日最后一秒）"""
		monday_of_that_week, sunday_of_that_week = cls.start_and_end_date_of_one_week(one_date_point)
		monday_str = monday_of_that_week.strftime("%Y-%m-%d")
		sunday_str = sunday_of_that_week.strftime("%Y-%m-%d")
		return cls.start_of_date_utc(date_str=monday_str), cls.end_of_date_utc(date_str=sunday_str)
		
	
	@classmethod
	def start_and_end_date_of_one_month(cls, one_date_point):
		"""获取一个月的开始日期和结束日期"""
		first_day_of_that_month = one_date_point.replace(day=1)	
		if first_day_of_that_month.month == 12:
			last_day_of_that_month = first_day_of_that_month.replace(day=31)
		else:
			last_day_of_that_month = first_day_of_that_month.replace(month=first_day_of_that_month.month+1) - datetime.timedelta(days=1)
		return first_day_of_that_month, last_day_of_that_month
	
	@classmethod
	def local_time_delay_avoiding_sleeping_hours(cls, local_time, delay_hours, limit_hours):
		"""
		将本地时间延后delay_hours，并避开休息时间段（23点至8点间，13点至14点间），并且不能超过limit_hours
		返回：经过delaied_local_time，以及它与local_time之间的秒数差
		"""
		if limit_hours < delay_hours:
			return None, None
		
		# shift: 如果是23点，则向前-1变为22点，或向后+9变为8点
		not_allow_hours_and_shift = {23:(-1,9), 0:(-2,8), 1:(-3,7), 2:(-4,6), 3:(-5,5), 4:(-6,4), 5:(-7,3), 6:(-8,2), 7:(-9,1), 13:(-1,1)}
		temp_time = local_time + relativedelta(hours=delay_hours)
		limit_time_min = local_time
		limit_time_max = local_time + relativedelta(hours=limit_hours)
		if temp_time.hour in not_allow_hours_and_shift:
			# 落在休息时段，需要延后或提前
			temp_time_backward = temp_time + relativedelta(hours=not_allow_hours_and_shift.get(temp_time.hour)[0])
			temp_time_forward = temp_time + relativedelta(hours=not_allow_hours_and_shift.get(temp_time.hour)[1])
			if limit_time_min < temp_time_forward < limit_time_max:
				#优先向后推延
				return temp_time_forward, (temp_time_forward - local_time).total_seconds()
			elif limit_time_min < temp_time_backward < limit_time_max:
				#其次，尝试提前
				return temp_time_backward, (temp_time_backward - local_time).total_seconds()
			else:
				return None
		else:
			#不在休息时段，可以直接用
			return temp_time, (temp_time - local_time).total_seconds()
	
	@classmethod
	def daterange(cls, start_date, end_date):
		for n in range(int ((end_date - start_date).days)):
			yield start_date + datetime.timedelta(n)

	@classmethod
	def get_week_range(cls, dst_dt=None, utc=True):
		"""
		获得目标local日期所在周的区间
		:param dst_dt: 目标local日期，默认为今天
		:param utc: 是否返回utc日期
		:return: [first_datetime, last_datetime]
		:rtype: list
		:author: Unknown @ 20150512
		"""
		if not dst_dt:
			dst_dt = cls.now_local()
		else:
			dst_dt = cls.to_local_datetime(dst_dt)
		first_dt = dst_dt - datetime.timedelta(dst_dt.weekday())
		first_dt = datetime.datetime(first_dt.year, first_dt.month, first_dt.day, 0, 0, 0, tzinfo=first_dt.tzinfo)
		last_dt = dst_dt + datetime.timedelta(6 - dst_dt.weekday())
		last_dt = datetime.datetime(last_dt.year, last_dt.month, last_dt.day, 23, 59, 59, tzinfo=last_dt.tzinfo)
		if utc:
			first_dt = DatetimeUtils.to_utc_datetime(first_dt)
			last_dt = DatetimeUtils.to_utc_datetime(last_dt)
		return first_dt, last_dt

	@classmethod
	def get_month_range(cls, dst_dt=None, utc=True):
		"""
		获得目标local日期所在月的区间
		:param dst_dt: 目标local日期，默认为今天
		:param utc: 是否返回utc时间
		:return: [first_datetime, last_datetime]
		:rtype: list
		:author: Unknown @ 20150512
		"""
		import calendar
		if not dst_dt:
			dst_dt = cls.now_local()
		else:
			dst_dt = cls.to_local_datetime(dst_dt)
		first_dt = datetime.datetime(dst_dt.year, dst_dt.month, 1, 0, 0, 0, tzinfo=dst_dt.tzinfo)
		end_day = calendar.monthrange(dst_dt.year, dst_dt.month)[1]
		last_dt = datetime.datetime(dst_dt.year, dst_dt.month, end_day, 23, 59, 59, tzinfo=dst_dt.tzinfo)
		if utc:
			first_dt = DatetimeUtils.to_utc_datetime(first_dt)
			last_dt = DatetimeUtils.to_utc_datetime(last_dt)
		return first_dt, last_dt

	@classmethod
	def get_day_range(cls, dst_dt=None, utc=True):
		"""
		获得目标local日期所在日的区间
		:param dst_dt: 目标local日期，默认为今天
		:param utc: 是否返回utc时间, 否则返回local时间
		:return: [first_datetime, last_datetime]
		:rtype: list
		:author: Unknown @ 20150512
		"""
		if not dst_dt:
			dst_dt = cls.now_local()
		else:
			dst_dt = cls.to_local_datetime(dst_dt)
		first_dt = datetime.datetime(dst_dt.year, dst_dt.month, dst_dt.day, 0, 0, 0, tzinfo=dst_dt.tzinfo)
		last_dt = datetime.datetime(dst_dt.year, dst_dt.month, dst_dt.day, 23, 59, 59, tzinfo=dst_dt.tzinfo)
		if utc:
			first_dt = DatetimeUtils.to_utc_datetime(first_dt)
			last_dt = DatetimeUtils.to_utc_datetime(last_dt)
		return first_dt, last_dt

	@classmethod
	def get_last_week_range(cls, dst_dt=None, utc=True):
		"""
		获取目标local日期上一周的区间
		:param dst_dt: 目标local日期， 默认为今天
		:param utc: 是否返回utc时间
		:return: [first_datetime, last_datetime]
		:rtype: list
		:author: Unknown @ 20150512
		"""
		if not dst_dt:
			dst_dt = cls.now_local()
		else:
			dst_dt = cls.to_local_datetime(dst_dt)
		return cls.get_week_range(cls.get_week_range(dst_dt, utc=False)[0] - datetime.timedelta(days=1), utc)


class WeightDataSet(object):
	"""
	带权重的样本数据集合
	"""
	RATIO_TOTAL = 10000.0

	class RatioError(Exception):
		"""
		随机因子异常
		"""
		pass

	class Node:
		"""
		数据节点
		"""
		def __init__(self, index, data, weight, min_limit, max_limit):
			self.index = index
			self.data = data
			self.weight = weight
			self.min_limit = min_limit
			self.max_limit = max_limit

		def __contains__(self, ratio):
			return self.min_limit <= ratio <= self.max_limit

	def __init__(self):
		self.total_weight = 0
		self.nodes = []

		self.init_nodes()

	def init_nodes(self):
		"""
		初始化数据节点
		"""
		raise NotImplementedError()

	def add_node(self, index, data, weight):
		"""
		添加数据节点
		:param index: <object: 数据在原数据集中的索引>
		:param data: <object: 数据>
		:param weight: <int: 权重>
		"""
		self.nodes.append(self.Node(index, data, weight, self.total_weight, self.total_weight + weight))
		self.total_weight += weight

	def get_node(self, ratio):
		"""
		由权重因子获得节点对象
		:param ratio: <int: 1 ~ RATIO_TOTAL 整数>
		:return: <Node>
		"""
		if 1 <= ratio <= self.RATIO_TOTAL:
			index = int(self.total_weight * (ratio / self.RATIO_TOTAL))
			for node in self.nodes:
				if index in node:
					return node
		else:
			raise self.RatioError('ratio should be in %s, but given %s' % ((1, self.RATIO_TOTAL), ratio))

	@property
	def random_node(self):
		"""
		:return: <Node: 随机数据节点，概率由节点权重决定>
		"""
		import random
		return self.get_node(random.randint(1, int(self.RATIO_TOTAL)))

	@property
	def random_data(self):
		"""
		:return: <object: 随机数据,概率由节点权重决定>
		"""
		return self.random_node.data

